#pragma once

template<typename T>
class Math
{
public:
	// Wrappers to hide implementations of functions.  The ACos and ASin
	// functions clamp the input argument to [-1,1] to avoid NaN issues
	// when the input is slightly larger than 1 or slightly smaller than -1.
	// Other functions have the potential for using a fast and approximate
	// algorithm rather than calling the standard math library functions.
	static T ACos (T value);
	static T ASin (T value);
	static T ATan (T value);
	static T ATan2 (T y, T x);
	static T Ceil (T value);
	static T Cos (T value);
	static T Exp (T value);
	static T FAbs (T value);
	static T Floor (T value);
	static T FMod (T x, T y);
	static T InvSqrt (T value);
	static T Log (T value);
	static T Log2 (T value);
	static T Log10 (T value);
	static T Pow (T base, T exponent);
	static T Sin (T value);
	static T Sqr (T value);
	static T Sqrt (T value);
	static T Tan (T value);

	// Return -1 if the input is negative, 0 if the input is zero, and +1
	// if the input is positive.
	static int Sign (int value);
	static T Sign (T value);

	// Generate a random number in [0,1].  The random number generator may
	// be seeded by a first call to UnitRandom with a positive seed.
	static T UnitRandom (unsigned int seed = 0);

	// Generate a random number in [-1,1].  The random number generator may
	// be seeded by a first call to SymmetricRandom with a positive seed.
	static T SymmetricRandom (unsigned int seed = 0);

	// Generate a random number in [min,max].  The random number generator may
	// be seeded by a first call to IntervalRandom with a positive seed.
	static T IntervalRandom (T min, T max, unsigned int seed = 0);

	// Clamp the input to the specified interval [min,max].
	static T Clamp (T value, T minValue, T maxValue);

	// Clamp the input to [0,1].
	static T Saturate (T value);

	// Fast evaluation of trigonometric and inverse trigonometric functions
	// using polynomial approximations.  The speed ups vary with processor.

	// The input must be in [0,pi/2].
	static T FastSin0 (T angle);
	static T FastSin1 (T angle);

	// The input must be in [0,pi/2]
	static T FastCos0 (T angle);
	static T FastCos1 (T angle);

	// The input must be in [0,pi/4].
	static T FastTan0 (T angle);
	static T FastTan1 (T angle);

	// The input must be in [0,1].
	static T FastInvSin0 (T value);
	static T FastInvSin1 (T value);

	// The input must be in [0,1].
	static T FastInvCos0 (T value);
	static T FastInvCos1 (T value);

	// The input must be in [-1,1]. 
	static T FastInvTan0 (T value);
	static T FastInvTan1 (T value);

	// Fast approximations to exp(-x).  The input x must be in [0,infinity).
	static T FastNegExp0 (T value);
	static T FastNegExp1 (T value);
	static T FastNegExp2 (T value);
	static T FastNegExp3 (T value);

	// Common constants.
	static const T EPSILON;
	static const T ZERO_TOLERANCE;
	static const T MAX_REAL;
	static const T PI;
	static const T TWO_PI;
	static const T HALF_PI;
	static const T INV_PI;
	static const T INV_TWO_PI;
	static const T DEG_TO_RAD;
	static const T RAD_TO_DEG;
	static const T LN_2;
	static const T LN_10;
	static const T INV_LN_2;
	static const T INV_LN_10;
	static const T SQRT_2;
	static const T INV_SQRT_2;
	static const T SQRT_3;
	static const T INV_SQRT_3;
};

typedef Math<float> Mathf;
typedef Math<double> Mathd;


template <typename T>
T Math<T>::ACos (T value)
{
	if (-(T)1 < value)
	{
		if (value < (T)1)
		{
			return acos(value);
		}
		else
		{
			return (T)0;
		}
	}
	else
	{
		return PI;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::ASin (T value)
{
	if (-(T)1 < value)
	{
		if (value < (T)1)
		{
			return asin(value);
		}
		else
		{
			return HALF_PI;
		}
	}
	else
	{
		return -HALF_PI;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::ATan (T value)
{
	return atan(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::ATan2 (T y, T x)
{
	if (x != (T)0 || y != (T)0)
	{
		return atan2(y, x);
	}
	else
	{
		// Mathematically, ATan2(0,0) is undefined, but ANSI standards
		// require the function to return 0.
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Ceil (T value)
{
	return ceil(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Cos (T value)
{
	return cos(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Exp (T value)
{
	return exp(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FAbs (T value)
{
	return fabs(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Floor (T value)
{
	return floor(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FMod (T x, T y)
{
	if (y != (T)0)
	{
		return fmod(x, y);
	}
	else
	{
		assertion(false, "Zero input to FMod\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::InvSqrt (T value)
{
	if (value != (T)0)
	{
		return ((T)1)/sqrt(value);
	}
	else
	{
		assertion(false, "Division by zero in InvSqr\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Log (T value)
{
	if (value > (T)0)
	{
		return log(value);
	}
	else
	{
		//assertion(false, "Nonpositive input to Log\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Log2 (T value)
{
	if (value > (T)0)
	{
		return Math<T>::INV_LN_2 * log(value);
	}
	else
	{
		//assertion(false, "Nonpositive input to Log2\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Log10 (T value)
{
	if (value > (T)0)
	{
		return Math<T>::INV_LN_10 * log(value);
	}
	else
	{
		//assertion(false, "Nonpositive input to Log10\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Pow (T base, T exponent)
{
	if (base >= (T)0)
	{
		return pow(base, exponent);
	}
	else
	{
		//assertion(false, "Negative base not allowed in Pow\n");
		return Math<T>::MAX_REAL;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Sin (T value)
{
	return sin(value);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Sqr (T value)
{
	return value*value;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Sqrt (T value)
{
	if (value >= (T)0)
	{
		return sqrt(value);
	}
	else
	{
		//assertion(false, "Negative input to Sqrt\n");
		return (T)0;
	}
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Tan (T value)
{
	return tan(value);
}
//----------------------------------------------------------------------------
template <typename T>
int Math<T>::Sign (int value)
{
	if (value > 0)
	{
		return 1;
	}

	if (value < 0)
	{
		return -1;
	}

	return 0;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Sign (T value)
{
	if (value > (T)0)
	{
		return (T)1;
	}

	if (value < (T)0)
	{
		return (T)-1;
	}

	return (T)0;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::UnitRandom (unsigned int seed)
{
	if (seed > 0)
	{
		srand(seed);
	}

	T ratio = ((T)rand())/((T)(RAND_MAX));
	return (T)ratio;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::SymmetricRandom (unsigned int seed)
{
	return ((T)2)*UnitRandom(seed) - (T)1;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::IntervalRandom (T min, T max, unsigned int seed)
{
	return min + (max - min)*UnitRandom(seed);
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Clamp (T value, T minValue, T maxValue)
{
	if (value <= minValue)
	{
		return minValue;
	}
	if (value >= maxValue)
	{
		return maxValue;
	}
	return value;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::Saturate (T value)
{
	if (value <= (T)0)
	{
		return (T)0;
	}
	if (value >= (T)1)
	{
		return (T)1;
	}
	return value;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastSin0 (T angle)
{
	T angleSqr = angle*angle;
	T result = (T)7.61e-03;
	result *= angleSqr;
	result -= (T)1.6605e-01;
	result *= angleSqr;
	result += (T)1.0;
	result *= angle;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastSin1 (T angle)
{
	T angleSqr = angle*angle;
	T result = -(T)2.39e-08;
	result *= angleSqr;
	result += (T)2.7526e-06;
	result *= angleSqr;
	result -= (T)1.98409e-04;
	result *= angleSqr;
	result += (T)8.3333315e-03;
	result *= angleSqr;
	result -= (T)1.666666664e-01;
	result *= angleSqr;
	result += (T)1.0;
	result *= angle;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastCos0 (T angle)
{
	T angleSqr = angle*angle;
	T result = (T)3.705e-02;
	result *= angleSqr;
	result -= (T)4.967e-01;
	result *= angleSqr;
	result += (T)1.0;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastCos1 (T angle)
{
	T angleSqr = angle*angle;
	T result = -(T)2.605e-07;
	result *= angleSqr;
	result += (T)2.47609e-05;
	result *= angleSqr;
	result -= (T)1.3888397e-03;
	result *= angleSqr;
	result += (T)4.16666418e-02;
	result *= angleSqr;
	result -= (T)4.999999963e-01;
	result *= angleSqr;
	result += (T)1.0;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastTan0 (T angle)
{
	T angleSqr = angle*angle;
	T result = (T)2.033e-01;
	result *= angleSqr;
	result += (T)3.1755e-01;
	result *= angleSqr;
	result += (T)1.0;
	result *= angle;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastTan1 (T angle)
{
	T angleSqr = angle*angle;
	T result = (T)9.5168091e-03;
	result *= angleSqr;
	result += (T)2.900525e-03;
	result *= angleSqr;
	result += (T)2.45650893e-02;
	result *= angleSqr;
	result += (T)5.33740603e-02;
	result *= angleSqr;
	result += (T)1.333923995e-01;
	result *= angleSqr;
	result += (T)3.333314036e-01;
	result *= angleSqr;
	result += (T)1.0;
	result *= angle;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvSin0 (T value)
{
	T root = Math<T>::Sqrt(FAbs((T)1 - value));
	T result = -(T)0.0187293;
	result *= value;
	result += (T)0.0742610;
	result *= value;
	result -= (T)0.2121144;
	result *= value;
	result += (T)1.5707288;
	result = HALF_PI - root*result;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvSin1 (T value)
{
	T root = Math<T>::Sqrt(FAbs((T)1 - value));
	T result = -(T)0.0012624911;
	result *= value;
	result += (T)0.0066700901;
	result *= value;
	result -= (T)0.0170881256;
	result *= value;
	result += (T)0.0308918810;
	result *= value;
	result -= (T)0.0501743046;
	result *= value;
	result += (T)0.0889789874;
	result *= value;
	result -= (T)0.2145988016;
	result *= value;
	result += (T)1.5707963050;
	result = HALF_PI - root*result;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvCos0 (T value)
{
	T root = Math<T>::Sqrt(FAbs((T)1 - value));
	T result = -(T)0.0187293;
	result *= value;
	result += (T)0.0742610;
	result *= value;
	result -= (T)0.2121144;
	result *= value;
	result += (T)1.5707288;
	result *= root;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvCos1 (T value)
{
	T root = Math<T>::Sqrt(FAbs((T)1 - value));
	T result = -(T)0.0012624911;
	result *= value;
	result += (T)0.0066700901;
	result *= value;
	result -= (T)0.0170881256;
	result *= value;
	result += (T)0.0308918810;
	result *= value;
	result -= (T)0.0501743046;
	result *= value;
	result += (T)0.0889789874;
	result *= value;
	result -= (T)0.2145988016;
	result *= value;
	result += (T)1.5707963050;
	result *= root;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvTan0 (T value)
{
	T valueSqr = value*value;
	T result = (T)0.0208351;
	result *= valueSqr;
	result -= (T)0.085133;
	result *= valueSqr;
	result += (T)0.180141;
	result *= valueSqr;
	result -= (T)0.3302995;
	result *= valueSqr;
	result += (T)0.999866;
	result *= value;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastInvTan1 (T value)
{
	T valueSqr = value*value;
	T result = (T)0.0028662257;
	result *= valueSqr;
	result -= (T)0.0161657367;
	result *= valueSqr;
	result += (T)0.0429096138;
	result *= valueSqr;
	result -= (T)0.0752896400;
	result *= valueSqr;
	result += (T)0.1065626393;
	result *= valueSqr;
	result -= (T)0.1420889944;
	result *= valueSqr;
	result += (T)0.1999355085;
	result *= valueSqr;
	result -= (T)0.3333314528;
	result *= valueSqr;
	result += (T)1;
	result *= value;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastNegExp0 (T value)
{
	T result = (T)0.0038278;
	result *= value;
	result += (T)0.0292732;
	result *= value;
	result += (T)0.2507213;
	result *= value;
	result += (T)1;
	result *= result;
	result *= result;
	result = ((T)1)/result;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastNegExp1 (T value)
{
	T result = (T)0.00026695;
	result *= value;
	result += (T)0.00227723;
	result *= value;
	result += (T)0.03158565;
	result *= value;
	result += (T)0.24991035;
	result *= value;
	result += (T)1;
	result *= result;
	result *= result;
	result = ((T)1)/result;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastNegExp2 (T value)
{
	T result = (T)0.000014876;
	result *= value;
	result += (T)0.000127992;
	result *= value;
	result += (T)0.002673255;
	result *= value;
	result += (T)0.031198056;
	result *= value;
	result += (T)0.250010936;
	result *= value;
	result += (T)1;
	result *= result;
	result *= result;
	result = ((T)1)/result;
	return result;
}
//----------------------------------------------------------------------------
template <typename T>
T Math<T>::FastNegExp3 (T value)
{
	T result = (T)0.0000006906;
	result *= value;
	result += (T)0.0000054302;
	result *= value;
	result += (T)0.0001715620;
	result *= value;
	result += (T)0.0025913712;
	result *= value;
	result += (T)0.0312575832;
	result *= value;
	result += (T)0.2499986842;
	result *= value;
	result += (T)1;
	result *= result;
	result *= result;
	result = ((T)1)/result;
	return result;
}
//----------------------------------------------------------------------------